#pragma once

#include <xaudio2.h>
#include <vector>



//+------------------------------------------------------------------+
inline UINT32 BuffersQueued(IXAudio2SourceVoice *voice)
{
	XAUDIO2_VOICE_STATE state;
	voice->GetState(&state);
	return state.BuffersQueued;
}



//+==================================================================+
class Audio
{
	IXAudio2										*pXAudio2;
	IXAudio2MasteringVoice					*pMasteringVoice;
//	std::vector<IXAudio2SourceVoice*>	pSourceVoices;

//+------------------------------------------------------------------+
//	no copying
	Audio					(const Audio&);
	Audio& operator =	(const Audio&);

public:
//////////////////////////////////////////////////////////////////////
//	Must have initialized the COM library before: "CoInitializeEx()"
	Audio();
	~Audio();

//+------------------------------------------------------------------+
//	Creates and returns a new source voice.
//	You must call "DestroyVoice", when it's no longer used.
	IXAudio2SourceVoice		*newVoice(WORD channels = 2, WORD bitsPerSample = 16, DWORD sampleRate = 44100, IXAudio2VoiceCallback *callback = NULL);

//+------------------------------------------------------------------+
	IXAudio2						*getXAudio2()				{	return pXAudio2;					}
	IXAudio2MasteringVoice	*getMasteringVoice()		{	return pMasteringVoice;			}

};



#define FILL_AND_SUBMIT_BUFFER	0x0100	// Used in XAUDIO2_BUFFER.Flags
#define BLOCK_BUFFER					0x0200	// Used in XAUDIO2_BUFFER.Flags

//+==================================================================+
class BufferedVoice : public IXAudio2VoiceCallback
{
//+------------------------------------------------------------------+
//	no copying
	BufferedVoice					(const BufferedVoice&);
	BufferedVoice& operator =	(const BufferedVoice&);

	CRITICAL_SECTION	state_access;		// state must be synchronized, as e.g. voices must not be destroyed while accessed

	HANDLE threadHandle;						// nonzero, until fill buffer thread exits

protected:
//////////////////////////////////////////////////////////////////////
//+------------------------------------------------------------------+
// Arrays for audio frame buffers
	std::vector<XAUDIO2_BUFFER>			buffers;
//+------------------------------------------------------------------+
// Array with all our allocated voices.
//	Every index is a valid voice, possibly idle.
	std::vector<IXAudio2SourceVoice*>	voices;
	Audio *const								audio;
//+------------------------------------------------------------------+
// PCM format of all voices
	WORD											channels;
	WORD											bitsPerSample;
	DWORD											sampleRate;
	float											volume;
	bool											blockBuffers;

//+------------------------------------------------------------------+
// Should set the buffer to the next chunk of audio data.
// Can also set XAUDIO2_END_OF_STREAM flag; then no new "fillBuffer"
// requests are issued after this one finishes playing.
//	@return true on success, false if data is not ready
//	@note If false is returned, the buffer is not submitted.
	virtual bool fillBuffer(unsigned bufferIndex)
	{
		return false;
	}
	
//+------------------------------------------------------------------+
//	Submit a buffer to a voice
	void submitBuffer(unsigned buffer = 0, unsigned voice = 0);

//+------------------------------------------------------------------+
//	Worker thread, which fills empty buffers.
//	Only useful for streaming voices (i.e. buffers.size() > 1).
	static DWORD WINAPI FillBufferThread(LPVOID lpParam);

//+------------------------------------------------------------------+
/*	Derived classes should put the following in their constructors:
	if(buffers.size() > 1)
		createFillBufferThread()

	and in their destructor:
	exitFillBufferThread();

*/	void createFillBufferThread();
	void exitFillBufferThread();

public:
//////////////////////////////////////////////////////////////////////
//	You should decide between using several buffers with one voice
//	(for streaming), or just one buffer which automatically uses more
//	voices, when "play" is called in rapid succession.
	BufferedVoice(Audio *audioRef, DWORD numBuffers = 1, float volume = 1.0f, bool blockBuffers = false);
	~BufferedVoice();

//+------------------------------------------------------------------+
	enum State {
		playing, paused, stopped, exit, fade_in, fade_out
	} state;

//+------------------------------------------------------------------+
//	Allocates a voice with these specs and frees prior voices.
	void initialize(WORD channels = 2, WORD bitsPerSample = 16, DWORD sampleRate = 44100);
//+------------------------------------------------------------------+
//	Destroy all allocated voices.
	void destroyVoices();

//+------------------------------------------------------------------+
//	Uses an idle voice or allocates a new one, starts playback, and
//	returns the used voice index.
	virtual int play();
//+------------------------------------------------------------------+
//	Stops the voice (default = all voices), but preserves buffers.
	virtual void pause(int voice = -1);
//+------------------------------------------------------------------+
//	Continues playing previously paused voices.
	virtual void resume(int voice = -1);
//+------------------------------------------------------------------+
//	Stops voice and flushes its buffers.
	virtual void stop(int voice = -1);

//+------------------------------------------------------------------+
//	Unsynchronized state access
	State getState()				const		{	return state;				}

//+------------------------------------------------------------------+
	WORD	getNumChannels()		const		{	return channels;			}
	WORD	getBitsPerSample()	const		{	return bitsPerSample;	}
	DWORD	getSampleRate()		const		{	return sampleRate;		}
	DWORD	getNumBuffers()		const		{	return buffers.size();	}
	void	setBlockBuffers(bool b)			{	blockBuffers = b;			}

//+------------------------------------------------------------------+
//	Sets volume of the voice and all new voices.
	void	setVolume(float vol, int voice = -1);
	float	getVolume(unsigned voice = 0) const;
	void	fadeIn();
	void	fadeOut();

//+------------------------------------------------------------------+
//	Total number of samples played by this voice.
//	Resolution is the equvivalent of 10ms.
	UINT64 playedSamples(unsigned voice = 0) const
	{
		if(voice >= voices.size())
			return 0;
		XAUDIO2_VOICE_STATE state;
		voices[voice]->GetState(&state);
		return state.SamplesPlayed;
	}

//+------------------------------------------------------------------+
//	Total number of seconds played by this voice. Resolution is 10ms.
	double playedTime(unsigned voice = 0) const
	{
		UINT64 n = playedSamples(voice);
		return n ? double(n) / double(sampleRate) : 0.0;
	}

//+------------------------------------------------------------------+
//	Buffer can be reused.
	void unblockBuffer(unsigned buffer)
	{
		buffers[buffer].Flags &= ~BLOCK_BUFFER;
	}

//+------------------------------------------------------------------+
	void WINAPI OnStreamEnd()													{}
	void WINAPI OnVoiceProcessingPassEnd()									{}
	void WINAPI OnVoiceProcessingPassStart(UINT32 SamplesRequired)	{}
	void WINAPI OnVoiceError	(void *pBufferContext, HRESULT)		{}
	void WINAPI OnLoopEnd		(void *pBufferContext)					{}
	void WINAPI OnBufferStart	(void *pBufferContext)					{}
	void WINAPI OnBufferEnd		(void *pBufferContext)
	{
		UINT32 &flags = buffers[(u64) pBufferContext].Flags;

	//	Set state to stopped after the last buffer.
	//	Mark buffer for reuse, unless the stream ended or was stopped.
		if(flags & XAUDIO2_END_OF_STREAM)
		{
			EnterCriticalSection(&state_access);
			state = stopped;
			LeaveCriticalSection(&state_access);
		}
		else if(state != stopped)
			flags |= FILL_AND_SUBMIT_BUFFER;

	//	Still wait for application to also release buffer?
		if(blockBuffers)
			flags |=  BLOCK_BUFFER;
		else
			flags &= ~BLOCK_BUFFER;
	}

};

